<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { useMessage } from 'naive-ui'
import { useRoute } from 'vue-router'
import { db } from '@/db/db'
import { debounce } from 'radash'
import { DOC_FORMAT } from '@/constant/documentFormat'
import { Settings } from '@vicons/ionicons5'
import {
  AlignSpaceAroundHorizontal20Filled as AlignSpace,
  ArrowCircleLeft32Filled,
  ArrowCircleRight32Filled,
} from '@vicons/fluent'
import { RestorePageRound, LibraryBooksRound } from '@vicons/material'
import { useReadSettingStore } from '@/stores/readSetting'

import dayjs from 'dayjs'
import useEpubParse from '@/hooks/epub/useEpubParse'
import useAddEventLister from '@/hooks/useAddEventLister'
import usePdfParse from '@/hooks/pdfjs/usePdfParse'
import useDocumentTitle from '@/hooks/useDocumentTitle'
import ReaderImg from '@/components/ReaderImg.vue'
import ReaderMenu from '@/components/ReaderMenu.vue'
import useComponentRef from '@/hooks/useComponentRef'
import MenuItem from '@/components/MenuItem.vue'
import useZoomArea from '@/hooks/useZoomArea'
import useZipParse from '@/hooks/zip/useZipParse'

const message = useMessage()
const route = useRoute()
const id = route.params.id
const settingStore = useReadSettingStore() // 读取设置

/**
 * 读取数据
 */
const blobImgs = ref<string[]>([])
const blobFuncs = ref<Promise<string>[]>([])
const len = 4 // 预加载页数
/**
 * 涟漪式执行数组中的方法：从中间索引向两端扩散，支持控制最大扩散次数
 * @param {Array<Function>} methods - 结果数组
 * @param {Array<Function>} methods - 待执行的方法数组
 * @param {number} centerIndex - 中间起始索引
 * @param {number} [maxRipples] - 最大涟漪扩散次数（undefined/0 表示执行全部）
 * @returns void
 */
async function rippleExecuteLoadBook(
  results: string[],
  methods: Promise<string>[],
  centerIndex: number,
  maxRipples: number | undefined,
) {
  // 边界检查
  const hasUndefined = results.find((i) => i === undefined) === undefined
  // console.log('🚀 ~ rippleExecuteLoadBook ~ hasUndefined:', hasUndefined)
  if (!Array.isArray(methods) || methods.length === 0 || !hasUndefined) {
    return []
  }

  // 确保中心索引在有效范围内
  const center = Math.max(0, Math.min(centerIndex, methods.length - 1))

  const executed = new Set() // 记录已执行的索引
  let step = 0 // 扩散步数（0:中心，1:左右各1步，2:左右各2步...）

  // 先执行中心元素
  if (!results[center]) results[center] = await methods[center]
  executed.add(center)

  // 计算最大执行步数：如果指定了maxRipples则用其值，否则为无限（直到全部执行）
  const hasMaxRipples = typeof maxRipples === 'number' && maxRipples > 0
  const maxSteps = hasMaxRipples ? maxRipples : Infinity

  // 循环扩散执行
  while (executed.size < methods.length && step < maxSteps) {
    step++ // 步数从1开始（0是中心）
    // 左侧索引 = 中心 - 步数
    const leftIndex = center - step
    // 右侧索引 = 中心 + 步数
    const rightIndex = center + step

    // 执行左侧元素（如果有效且未执行）
    if (leftIndex >= 0 && !executed.has(leftIndex)) {
      if (!results[leftIndex]) results[leftIndex] = await methods[leftIndex]
      executed.add(leftIndex)
    }

    // 执行右侧元素（如果有效且未执行）
    if (rightIndex < methods.length && !executed.has(rightIndex)) {
      if (!results[rightIndex]) results[rightIndex] = await methods[rightIndex]
      executed.add(rightIndex)
    }
  }
}
const loadBook = async (file: File | undefined, type: string, password?: string) => {
  if (!file) return message.error('书籍资源有误，请重新上传后再试')

  const arrayBuffer = await file.arrayBuffer()
  switch (type) {
    case DOC_FORMAT.EPUB:
      blobFuncs.value = await useEpubParse(arrayBuffer, message)
      blobImgs.value.length = blobFuncs.value.length
      calcBookData()
      break
    case DOC_FORMAT.PDF:
      blobFuncs.value = await usePdfParse(arrayBuffer)
      blobImgs.value.length = blobFuncs.value.length
      calcBookData()
      break
    case DOC_FORMAT.ZIP:
    case DOC_FORMAT.XZIP:
      blobFuncs.value = await useZipParse(file, password)
      blobImgs.value.length = blobFuncs.value.length
      calcBookData()
      break
    default:
      return message.error('sorry,解析文件有误')
  }

  // 记录打开时间
  db.books.update(id as string, { lastTime: dayjs().valueOf() })
}
const cover = ref('')
db.books
  .where({ id: id })
  .first()
  .then((foundBook) => {
    if (!foundBook) {
      message.error('没有找到匹配书籍')
      throw new Error('没有找到匹配书籍')
    }
    useDocumentTitle(foundBook.name + ' - Reader Farm')
    cover.value = foundBook.cover || ''
    if (foundBook?.currentIndex) current1.value = foundBook.currentIndex
    loadBook(foundBook.file, foundBook.type, foundBook?.password ?? undefined)
  })
// 加载数据
const calcBookData = async () => await rippleExecuteLoadBook(blobImgs.value, blobFuncs.value, current1.value, len)

/**
 * 翻页
 */
const offset = computed(() => settingStore.offset) // 每次翻页的偏移量，1表示单页，2表示双页
const current1 = ref(0)
const current2 = computed(() => current1.value + 1)
const menuWorking = computed(() => readerMenu.value?.working)
const prev = async () => {
  if (current1.value === 0) return
  current1.value -= offset.value
  calcBookData()
}
const next = async () => {
  if (current2.value >= blobImgs.value.length - 1) return
  current1.value += offset.value
  calcBookData()
}
const handlerClickListen = (e: MouseEvent) => {
  const target = e.target as HTMLElement
  if (scale.value === 1 && target.tagName.toLowerCase() === 'img') {
    if (menuWorking.value) {
      e.stopPropagation()
      e.preventDefault()
      return
    }
    const half = window.innerWidth / 2

    if (e.x >= half && isJanpaStyle.value) {
      prev() // 上一页
    } else {
      next() // 下一页
    }
  }
}
const handlerKeyboardUpListen = (e: KeyboardEvent) => {
  const { key } = e

  if (isJanpaStyle.value) {
    // 日式阅读风格（从右往左）：右箭头上一页，左箭头下一页
    if (key === 'ArrowRight') prev()
    else if (key === 'ArrowLeft') next()
  } else if (isNormalStyle.value) {
    // 正常阅读风格（从左往右）：左箭头上一页，右箭头下一页
    if (key === 'ArrowLeft') prev()
    else if (key === 'ArrowRight') next()
  }
}

/**
 * 鼠标样式
 */
const leftImg = '/left.png'
const rightImg = '/right.png'
const mousePosX = ref(0)
const cursorCustom = computed(() => {
  if (isDragging.value) return 'grabbing'
  if (mousePosX.value === 0) return 'none'
  if (scale.value === 1) {
    const turn = mousePosX.value < window.innerWidth / 2 ? leftImg : rightImg
    return `url(${turn}), auto`
  }
  return 'auto'
})
// 隐藏鼠标
const hideCursor = debounce({ delay: 5000 }, () => (mousePosX.value = 0))
watch(mousePosX, hideCursor)
const handlerMousemoveListen = (e: MouseEvent) => {
  console.log('move view')
  mousePosX.value = e.clientX

  const y = e.clientY
  // 如果鼠标在 tool-slider 内部，保持显示
  if (isMouseOverToolSlider.value) {
    progressShow.value = true
    return
  }
  // 否则根据鼠标位置决定显示/隐藏
  if (y < 50 || y > window.innerHeight - 50) {
    progressShow.value = true
  } else {
    progressShow.value = false
  }
}

// 鼠标进入 tool-slider
const handleMouseEnterToolSlider = () => {
  isMouseOverToolSlider.value = true
  progressShow.value = true
}
// 鼠标离开 tool-slider
const handleMouseLeaveToolSlider = () => {
  isMouseOverToolSlider.value = false
  // 检查鼠标是否还在页面边缘区域
  const y = mousePosX.value
  if (y < 50 || y > window.innerHeight - 50) {
    progressShow.value = true
  } else {
    progressShow.value = false
  }
}

/**
 * 工具栏
 */
const imgGap = computed(() => settingStore.imgGap)
const onGap = () => settingStore.setImgGap(!imgGap.value)
const crossPage = ref(true)
const onCrossPage = () => {
  current1.value += crossPage.value ? 1 : -1
  crossPage.value = !crossPage.value
}
const onOneOrTwo = () => {
  settingStore.setOffset(offset.value === 1 ? 2 : 1)
}
const progressShow = ref(false)
const isMouseOverToolSlider = ref(false)
const onDragend = async (value: number) => {
  if (value === current1.value) return
  current1.value = value
  await calcBookData()
}
const readDirection = computed(() => settingStore.direction)
const isJanpaStyle = computed(() => readDirection.value === 'rtl')
const isNormalStyle = computed(() => readDirection.value === 'ltr')
const onReadDirection = () => {
  settingStore.setDirection(isNormalStyle.value ? 'rtl' : 'ltr')
}

// 右键菜单
const readerMenu = useComponentRef(ReaderMenu)
const handleContextMenu = (e: MouseEvent) => {
  readerMenu.value?.handleContextMenu(e)
}

const {
  icons,
  isDragging,
  scale,
  setArea,
  handleZoomMouseDownListen,
  handleZoomMouseUpListen,
  handleZoomMouseMoveListen,
} = useZoomArea()
// 绑定事件
useAddEventLister({
  mousedown: handleZoomMouseDownListen,
  mouseup: handleZoomMouseUpListen,
  keyup: handlerKeyboardUpListen,
  mousemove: handlerMousemoveListen,
})
useAddEventLister({
  mousemove: handleZoomMouseMoveListen,
  click: handlerClickListen,
})

onMounted(() => {
  setArea('zoom-view')
})
onUnmounted(async () => {
  // 更新翻页的数据
  await db.books.update(id as string, { currentIndex: current1.value })
})
</script>

<template>
  <n-layout>
    <div class="viewer" id="zoom-view" :style="{ cursor: cursorCustom }" @contextmenu="handleContextMenu">
      <n-flex :wrap="false" justify="center" align="center"
        :style="{ flexDirection: isJanpaStyle ? 'row-reverse' : 'row', gap: imgGap ? '8px' : 'unset' }">
        <template v-if="offset === 1">
          <ReaderImg :src="blobImgs[current1]" :cover="cover" />
        </template>
        <template v-if="offset === 2">
          <ReaderImg :src="blobImgs[current1]" :cover="cover" :isStart="current1 === -1" />
          <ReaderImg :src="blobImgs[current2]" :cover="cover" :isEnd="current2 === blobImgs.length" />
        </template>
      </n-flex>
    </div>

    <ReaderMenu ref="readerMenu" :img1-src="blobImgs[current1]" :img2-src="blobImgs[current2]" />

    <n-float-button menu-trigger="hover" position="absolute" :right="30" :bottom="30">
      <n-icon color="#b7d07a">
        <Settings />
      </n-icon>

      <template #menu>
        <MenuItem v-if="offset === 2" :onClick="onGap" :icon="AlignSpace" tip="两张图片中间是否留空隙（此设置可能会由于浏览器的兼容性无效）" />
        <MenuItem :onClick="onCrossPage" :icon="RestorePageRound" tip="更改跨页匹配" />
        <MenuItem :onClick="onOneOrTwo" :icon="LibraryBooksRound" tip="单/双页 切换" />
        <MenuItem :onClick="onReadDirection" :icon="isJanpaStyle ? ArrowCircleLeft32Filled : ArrowCircleRight32Filled"
          :tip="`阅读方向(当前：${isJanpaStyle ? '从右往左' : '从左往右'})`" />
      </template>
    </n-float-button>

    <div class="tool-slider" :class="{ 'tool-slider--visible': progressShow }" @mouseenter="handleMouseEnterToolSlider"
      @mouseleave="handleMouseLeaveToolSlider">
      <n-flex :wrap="false" justify="center" align="center">
        <n-text>{{ `${current1 + 1}/${blobImgs.length}` }}</n-text>
        <n-slider v-model="current1" :value="current1" :step="1" :min="0" :max="blobImgs.length - 1" placement="bottom"
          :reverse="isJanpaStyle" @update:value="onDragend" />
        <n-flex :wrap="false" align="center" justify="flex-end" class="zoom-wrap">
          <n-tooltip trigger="hover" v-for="value in icons" :key="value.text">
            <template #trigger>
              <component :is="value.icon" style="cursor: pointer" />
            </template>
            {{ value.text }}
          </n-tooltip>
        </n-flex>
      </n-flex>
    </div>
  </n-layout>
</template>

<style scoped>
.viewer {
  height: 100vh;
  overflow: hidden;
  display: flex;
  justify-content: center;
  align-items: center;
}

.n-flex {
  max-height: 100vh;
  overflow: hidden;
}

.tool-slider {
  background-color: #000000a1;
  border-radius: 30px;
  border: 1px solid #ffffff61;
  padding: 8px 20px;
  position: fixed;
  bottom: 0;
  left: 50%;
  transform: translate(-50%, 100px);
  transition: all 0.5s ease;
  width: 500px;
  opacity: 0;
  visibility: hidden;
}

.tool-slider--visible {
  transform: translate(-50%, -30px);
  opacity: 1;
  visibility: visible;
}

.zoom-wrap {
  width: 180px;
  border-left: 1px solid #999;
  height: 100%;
}
</style>
